package com.zlkj.pro.modular.common.service.impl;

import cn.hutool.extra.spring.SpringUtil;
import cn.stylefeng.roses.kernel.config.api.context.ConfigContext;
import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.CertAlipayRequest;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayFundTransUniTransferModel;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.domain.Participant;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayFundTransUniTransferResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.beust.jcommander.internal.Maps;
import com.ijpay.alipay.AliPayApi;
import com.ijpay.alipay.AliPayApiConfig;
import com.ijpay.alipay.AliPayApiConfigKit;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.enums.TradeType;
import com.ijpay.core.kit.IpKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.model.UnifiedOrderModel;
import com.zlkj.pro.core.exception.BusinessException;
import com.zlkj.pro.core.util.LocalDateTimeUtils;
import com.zlkj.pro.core.util.MapUtil;
import com.zlkj.pro.modular.common.service.PayService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

import static com.zlkj.pro.core.exception.enums.BusinessExceptionEnum.*;

/**
 * @author 孙铭泽
 * @date 2022/3/28
 * @time 13:49
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class PayServiceImpl implements PayService {

    @Override
    public Map<String, String> wechatPay(String notifyPath, String outTradeNo, String body, BigDecimal totalFee) {
        String notifyUrl = String.format("%s%s", ConfigContext.me().getConfigValueNullable("PAYMENT_CALLBACK_HOST", String.class), notifyPath);
        String partnerKey = ConfigContext.me().getConfigValueNullable("WECHAT_PARTNER_KEY", String.class);
        String mchId = ConfigContext.me().getConfigValueNullable("WECHAT_MCHID", String.class);
        String appId = ConfigContext.me().getConfigValueNullable("WECHAT_APPID", String.class);
        HttpServletRequest request = HttpServletUtil.getRequest();
        String ip = IpKit.getRealIp(request);
        //发送请求
        String xmlResult = WxPayApi.pushOrder(Boolean.FALSE, UnifiedOrderModel
                .builder()
                .appid(appId)
                .mch_id(mchId)
                .nonce_str(WxPayKit.generateStr())
                .out_trade_no(outTradeNo)
                .body(body)
                .total_fee(String.valueOf(totalFee.multiply(new BigDecimal("100")).longValue()))
                .spbill_create_ip(ip)
                .notify_url(notifyUrl)
                .trade_type(TradeType.APP.getTradeType())
                .build()
                .createSign(partnerKey, SignType.MD5));
        Map<String, String> resultMap = WxPayKit.xmlToMap(xmlResult);
        String returnCode = resultMap.get("return_code");
        String returnMsg = resultMap.get("return_msg");
        if (!WxPayKit.codeIsOk(returnCode)) {
            log.info("微信支付调启失败【returnCode】Code:【{}】Msg【{}】", returnCode, returnMsg);
            throw new BusinessException(PAYMENT_FAILED, returnMsg);
        }
        String resultCode = resultMap.get("result_code");
        if (!WxPayKit.codeIsOk(resultCode)) {
            log.info("微信支付调启失败【resultCode】Code:【{}】Msg【{}】", resultCode, returnMsg);
            throw new BusinessException(PAYMENT_FAILED, returnMsg);
        }
        String prepayId = resultMap.get("prepay_id");
        return WxPayKit.appPrepayIdCreateSign(appId, mchId, prepayId, partnerKey, SignType.MD5);
    }

    @Override
    public String aliPay(String notifyPath, String outTradeNo, String subject, BigDecimal amount) {
        aliPayApiConfig(Boolean.FALSE);
        String notifyUrl = String.format("%s%s", ConfigContext.me().getConfigValueNullable("PAYMENT_CALLBACK_HOST", String.class), notifyPath);
        AlipayTradeAppPayModel alipayTradeAppPayModel = new AlipayTradeAppPayModel();
        alipayTradeAppPayModel.setTotalAmount(amount.toString());
        alipayTradeAppPayModel.setSubject(subject);
        alipayTradeAppPayModel.setOutTradeNo(outTradeNo);
        try {
            return AliPayApi.appPayToResponse(alipayTradeAppPayModel, notifyUrl).getBody();
        } catch (AlipayApiException e) {
            String errMsg = e.getErrMsg();
            String errCode = e.getErrCode();
            log.info("支付宝支付调启失败-Code:【{}】Msg:【{}】", errCode, errMsg);
            throw new BusinessException(PAYMENT_FAILED, errMsg);
        }
    }

    @Override
    public String aliPayWeb(String notifyPath, String returnPath, String outTradeNo, String subject, BigDecimal amount) {
        aliPayApiWebConfig();
        String notifyUrl = String.format("%s%s", ConfigContext.me().getConfigValueNullable("PAYMENT_CALLBACK_HOST", String.class), notifyPath);
        AlipayTradeWapPayModel alipayTradeAppPayModel = new AlipayTradeWapPayModel();
        alipayTradeAppPayModel.setTotalAmount(amount.toString());
        alipayTradeAppPayModel.setSubject(subject);
        alipayTradeAppPayModel.setOutTradeNo(outTradeNo);
        alipayTradeAppPayModel.setProductCode("QUICK_WAP_WAY");
        try {
            return AliPayApi.wapPayStr(alipayTradeAppPayModel, returnPath, notifyUrl);
        } catch (AlipayApiException e) {
            String errMsg = e.getErrMsg();
            String errCode = e.getErrCode();
            log.info("支付宝WEB支付调启失败-Code:【{}】Msg:【{}】", errCode, errMsg);
            throw new BusinessException(PAYMENT_FAILED, errMsg);
        }
    }

    @Override
    public <D extends PayCallback> String wechatCallback(String notifyXml, Class<D> callbackClass) {
        String partnerKey = ConfigContext.me().getConfigValueNullable("WECHAT_PARTNER_KEY", String.class);
        try {
            Map<String, String> notifyMap = WxPayKit.xmlToMap(notifyXml);
            log.info("微信回执参数【{}】", notifyMap);
            //回执验签
            if (!WxPayKit.verifyNotify(notifyMap, partnerKey)) {
                log.info("微信支付验签失败");
                return returnFail();
            }
            //解析是否支付成功
            String resultCode = notifyMap.get("result_code");
            if (WxPayKit.codeIsOk(resultCode)) {
                //平台流水号
                String outTradeNo = notifyMap.get("out_trade_no");
                //总金额(单位为分)
                String totalFee = notifyMap.get("total_fee");
                //微信支付订单号
                String transactionId = notifyMap.get("transaction_id");
                //支付完成时间
                String timeEnd = notifyMap.get("time_end");
                long time = new SimpleDateFormat(LocalDateTimeUtils.YYYYMMDDHHMMSS).parse(timeEnd).getTime();
                Date payTime = new Date(time);
                SpringUtil.getBean(callbackClass).callback(outTradeNo, new BigDecimal(totalFee).divide(new BigDecimal("100"), 2, BigDecimal.ROUND_DOWN), transactionId, payTime, JSON.toJSONString(notifyMap));
                return returnSuccess();
            } else {
                log.error("微信支付处理失败");
                return returnFail();
            }
        } catch (ParseException e) {
            log.error("微信支付解析失败【{}】", e.getMessage());
            return returnFail();
        }
    }

    @Override
    public <D extends PayCallback> String alipayCallback(Map<String, String[]> parameterMap, Class<D> callbackClass) {
        try {
            String alipayPublicKey = ConfigContext.me().getConfigValueNullable("ALIPAY_PUBLIC_KEY", String.class);
            // 获取支付宝POST过来反馈信息
            Map<String, String> params = MapUtil.toMap(parameterMap);
            log.info("支付宝app支付回调参数【{}】", params);
            boolean verifyResult = AlipaySignature.rsaCheckV1(params, alipayPublicKey, "UTF-8", "RSA2");
            if (verifyResult) {
                //交易状态
                String tradeStatus = params.get("trade_status");
                if ("TRADE_FINISHED".equals(tradeStatus) || "TRADE_SUCCESS".equals(tradeStatus)) {
                    //通知时间
                    String notifyTime = params.get("notify_time");
                    Date payTime = new SimpleDateFormat(LocalDateTimeUtils.YYYY_MM_DD_HH_MM_SS).parse(notifyTime);
                    //商户订单号
                    String outTradeNo = params.get("out_trade_no");
                    //支付宝交易号
                    String tradeNo = params.get("trade_no");
                    //实收金额
                    String receiptAmount = params.get("receipt_amount");
                    SpringUtil.getBean(callbackClass).callback(outTradeNo, new BigDecimal(receiptAmount), tradeNo, payTime, JSON.toJSONString(params));
                    return "success";
                }
                log.info("支付宝验签失败");
                return "failure";
            }
        } catch (AlipayApiException | ParseException e) {
            log.error("支付宝支付解析失败【{}】", e.getMessage());
        }
        log.info("支付宝验签失败");
        return "failure";
    }

    private String returnFail() {
        Map<String, String> restData = Maps.newHashMap();
        restData.put("return_code", "FAIL");
        restData.put("return_msg", "NO");
        return WxPayKit.toXml(restData);
    }

    private String returnSuccess() {
        Map<String, String> restData = Maps.newHashMap();
        restData.put("return_code", "SUCCESS");
        restData.put("return_msg", "OK");
        return WxPayKit.toXml(restData);
    }


    @Override
    public AlipayTradeRefundResponse alipayRefund(String outTradeNo, String totalAmount) {
        // 构造client
        AlipayClient alipayClient = getAlipayClient();
        aliPayApiConfig(Boolean.TRUE);
        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", outTradeNo);
        bizContent.put("refund_amount", totalAmount);
        bizContent.put("out_request_no", "HZ01RF001");
        // 返回参数选项，按需传入
        //JSONArray queryOptions = new JSONArray();
        //queryOptions.add("refund_detail_item_list");
        //bizContent.put("query_options", queryOptions);
        request.setBizContent(bizContent.toString());
        try {
            AlipayTradeRefundResponse response = alipayClient.certificateExecute(request);
            //调用成功 && 本次退款是否发生了资金变化
            if (!response.isSuccess() || !"Y".equals(response.getFundChange())) {
                String subMsg = response.getSubMsg();
                throw new BusinessException(ALIPAY_REFUND_ERROR, "支付宝退款失败" + subMsg);
            }
            return response;
        } catch (AlipayApiException e) {
            e.printStackTrace();
            log.error("支付宝退款出错了", e);
            throw new BusinessException(ALIPAY_REFUND_ERROR);
        }
    }

    private AlipayClient getAlipayClient() {
        CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
        // 设置网关地址
        certAlipayRequest.setServerUrl("https://openapi.alipay.com/gateway.do");
        // 设置应用Id
        String appId = ConfigContext.me().getConfigValueNullable("ALIPAY_APPID", String.class);
        certAlipayRequest.setAppId(appId);
        // 设置应用私钥
        String privateKey = ConfigContext.me().getConfigValueNullable("ALIPAY_PRIVATE_KEY", String.class);
        certAlipayRequest.setPrivateKey(privateKey);
        // 设置请求格式，固定值json
        certAlipayRequest.setFormat("JSON");
        // 设置字符集
        certAlipayRequest.setCharset("UTF-8");
        // 设置签名类型
        certAlipayRequest.setSignType("RSA2");
        String aliPayCertPath = this.getClass().getResource("/cert/alipayCertPublicKey_RSA2.crt").getPath();
        String aliPayRootCertPath = this.getClass().getResource("/cert/alipayRootCert.crt").getPath();
        String appCertPath = this.getClass().getResource("/cert/appCertPublicKey_2021002103628314.crt").getPath();
        // 设置应用公钥证书路径
        certAlipayRequest.setCertPath(appCertPath);
        // 设置支付宝公钥证书路径
        certAlipayRequest.setAlipayPublicCertPath(aliPayCertPath);
        // 设置支付宝根证书路径
        certAlipayRequest.setRootCertPath(aliPayRootCertPath);
        // 构造client
        try {
            return new DefaultAlipayClient(certAlipayRequest);
        } catch (AlipayApiException e) {
            e.printStackTrace();
            log.error("支付宝退款构建证书失败", e);
            throw new BusinessException(ALIPAY_FAILED_TO_BUILD_A_CERTIFICATE_PROCEDURE);
        }
    }

    private void aliPayApiWebConfig() {
        String appId = ConfigContext.me().getConfigValueNullable("ALIPAY_APPID", String.class);
        String alipayPublicKey = ConfigContext.me().getConfigValueNullable("ALIPAY_APPLICATION_PUBLIC_KEY", String.class);
        String privateKey = ConfigContext.me().getConfigValueNullable("ALIPAY_PRIVATE_KEY", String.class);
        AliPayApiConfig aliPayApiConfig = AliPayApiConfig.builder()
                .setServiceUrl("")
                .setAppId(appId)
                .setAliPayPublicKey(alipayPublicKey)
                .setPrivateKey(privateKey)
                .setCertModel(Boolean.FALSE)
                .build();
        AliPayApiConfigKit.setThreadLocalAliPayApiConfig(aliPayApiConfig);
    }

    private void aliPayApiConfig(boolean certModel) {
        String appId = ConfigContext.me().getConfigValueNullable("ALIPAY_APPID", String.class);
        String alipayPublicKey = ConfigContext.me().getConfigValueNullable("ALIPAY_APPLICATION_PUBLIC_KEY", String.class);
        String privateKey = ConfigContext.me().getConfigValueNullable("ALIPAY_PRIVATE_KEY", String.class);
        AliPayApiConfig aliPayApiConfig;
        try {
            if (!certModel) {
                aliPayApiConfig = AliPayApiConfig.builder()
                        .setServiceUrl("")
                        .setAppId(appId)
                        .setAliPayPublicKey(alipayPublicKey)
                        .setPrivateKey(privateKey)
                        .setCertModel(certModel)
                        .build();
            } else {
                String aliPayCertPath = this.getClass().getResource("/cert/alipayCertPublicKey_RSA2.crt").getPath();
                String aliPayRootCertPath = this.getClass().getResource("/cert/alipayRootCert.crt").getPath();
                String appCertPath = this.getClass().getResource("/cert/appCertPublicKey_2021002103628314.crt").getPath();
                aliPayApiConfig = AliPayApiConfig.builder()
                        .setServiceUrl("")
                        .setAppId(appId)
                        .setAliPayPublicKey(alipayPublicKey)
                        .setPrivateKey(privateKey)
                        .setCertModel(certModel)
                        .setAliPayCertPath(aliPayCertPath)
                        .setAliPayRootCertPath(aliPayRootCertPath)
                        .setAppCertPath(appCertPath)
                        .buildByCert();
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
            log.error("支付宝转账构建证书失败", e);
            throw new BusinessException(ALIPAY_FAILED_TO_BUILD_A_CERTIFICATE_PROCEDURE);
        }
        AliPayApiConfigKit.setThreadLocalAliPayApiConfig(aliPayApiConfig);
    }

    /**
     * 支付宝转账
     */
    @Override
    public AlipayFundTransUniTransferResponse transfer(String outBizNo, String identity, String name, BigDecimal transAmount) {
        try {
            aliPayApiConfig(Boolean.TRUE);
            AlipayFundTransUniTransferModel alipayFundTransUniTransferModel = new AlipayFundTransUniTransferModel();
            alipayFundTransUniTransferModel.setOutBizNo(outBizNo);
            Participant participant = new Participant();
            participant.setIdentity(identity);
            participant.setName(name);
            participant.setIdentityType("ALIPAY_LOGON_ID");
            alipayFundTransUniTransferModel.setPayeeInfo(participant);
            alipayFundTransUniTransferModel.setProductCode("TRANS_ACCOUNT_NO_PWD");
            alipayFundTransUniTransferModel.setTransAmount(transAmount.toString());
            alipayFundTransUniTransferModel.setBizScene("DIRECT_TRANSFER");
            log.info("支付宝转账请求【{}】", JSON.toJSONString(alipayFundTransUniTransferModel));
            AlipayFundTransUniTransferResponse alipayFundTransUniTransferResponse = AliPayApi.uniTransferToResponse(alipayFundTransUniTransferModel, "");
            log.info("支付宝转账响应【{}】", JSON.toJSONString(alipayFundTransUniTransferResponse));
            boolean success = alipayFundTransUniTransferResponse.isSuccess();
            if (success) {
                log.info("转账成功");
                return alipayFundTransUniTransferResponse;
            } else {
                String subMsg = alipayFundTransUniTransferResponse.getSubMsg();
                log.warn("转账失败【{}】", subMsg);
                throw new BusinessException(ALIPAY_TRANSFER_FAILED, "支付宝转账失败" + subMsg);
            }
        } catch (AlipayApiException e) {
            log.error("转账方法出错【{}】", e.getMessage());
            throw new BusinessException(ALIPAY_TRANSFER_FAILED);
        }
    }


    public static abstract class PayCallback {
        public abstract void callback(String outTradeNo, BigDecimal totalFee, String transactionId, Date payTime, String callbackJson) throws BusinessException;
    }


}
